﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace FiniteStateMachine
{
    public class FsmProcessor:IDisposable
    {
        #region members

        
        private Hashtable _FsmList=new Hashtable();
        private Queue _JobQueue=new Queue();
        private ArrayList _TimerList=new ArrayList();
        private DateTime _ExpiryTime;
        private string _Name;
        public string Name
        {
            get { return _Name; }
        }
        private Thread _Thread = null;
        public Thread Thread
        {
            get { return _Thread; }
        }

        private bool _TimerListChanged;

        public int NofFsm
        {
            get
            {
                lock(this)
                {
                    return this._FsmList.Count;
                }
            }
        }
        #endregion

        public FsmProcessor():this("")
        {
        }

        public FsmProcessor(string name)
        {
            lock(this)
            {
                this._TimerListChanged = false;
                this._Name = name;
                this._Thread=new Thread(new ThreadStart(this.ThreadProc));
                this.Thread.Start();
                Monitor.Pulse(this);
                Monitor.Wait(this);
            }
        }

        public void PushFsm(FiniteStateMachineBase fsm)
        {
            fsm.FsmProcessor = this;
            lock(this)
            {
                this._JobQueue.Enqueue(fsm);
                Monitor.Pulse(this);
            }
        }

        public void PopFsm(FiniteStateMachineBase fsm)
        {
            Debug.Assert(Thread.CurrentThread.GetHashCode()==this._Thread.GetHashCode());
            this._FsmList.Remove(fsm.GetHashCode());
            for(int i=0;i<this._TimerList.Count;i++)
            {
                if(((FsmTimerEvent)this._TimerList[i]).FiniteStateMachine.GetHashCode()==fsm.GetHashCode())
                {
                    ((FsmTimerEvent) this._TimerList[i]).Enabled = false;
                }
            }
        }

        public void TerminateFsm(FiniteStateMachineBase fsm)
        {
            if(Thread.CurrentThread.GetHashCode()==this._Thread.GetHashCode())
            {
                this.RemoveFsm(fsm);
            }
            else
            {
                TerminateFsmEvent evt=new TerminateFsmEvent();
                this.PushEvent(evt,fsm);
            }
        }

        public void TerminateAllFsm()
        {
            if(Thread.CurrentThread.GetHashCode()==this._Thread.GetHashCode())
            {
                this.RemoveAllFsms();
            }
            else
            {
                TerminateFsmEvent evt=new TerminateFsmEvent();
                this.PushEvent(evt, null);
            }
        }

        public void PushEvent(FsmEvent evt, FiniteStateMachineBase fsm)
        {
            lock(this)
            {
                evt.FiniteStateMachine = fsm;
                this._JobQueue.Enqueue(evt);
                Monitor.Pulse(this);
            }
        }

        public void PushEvent(FsmTimerEvent evt, FiniteStateMachineBase fsm)
        {
            lock(this)
            {
                evt.FiniteStateMachine = fsm;
                bool inserted = false;
                for(int i=0;i<this._TimerList.Count;i++)
                {
                    if(((FsmTimerEvent)this._TimerList[i]).ExpiryTime>=evt.ExpiryTime)
                    {
                        this._TimerList.Insert(i,evt);
                        inserted = true;
                        break;
                    }
                }
                if(!inserted)
                {
                    this._TimerList.Add(evt);
                }

                this._TimerListChanged = true;
                Monitor.Pulse(this);
            }
        }

        public void RemoveTimerEvent(FsmTimerEvent evt)
        {
            lock(this)
            {
                this._TimerList.Remove(evt);
                this._TimerList.Insert(0,evt);
                this._TimerListChanged = true;
                Monitor.Pulse(this);
            }
        }

        private void CheckTimerEvents()
        {
            this._TimerListChanged = false;
            if(this._TimerList.Count==0)
            {
                this._ExpiryTime = DateTime.MaxValue;
                return;
            }

            while(this._TimerList.Count>0 && ((FsmTimerEvent)this._TimerList[0]).Enabled==false)
            {
                this._TimerList.RemoveAt(0);
            }

            if(this._TimerList.Count==0)
            {
                this._ExpiryTime = DateTime.MaxValue;
                return;
            }

            DateTime currentTime = DateTime.Now;
            this._ExpiryTime = DateTime.MinValue;
            while(this._TimerList.Count>0 && (this._ExpiryTime ==DateTime.MinValue))
            {
                FsmTimerEvent timerEvt = (FsmTimerEvent) this._TimerList[0];
                this._ExpiryTime = timerEvt.ExpiryTime;
                if(this._ExpiryTime<=currentTime)
                {
                    this._ExpiryTime = DateTime.MinValue;
                    this._TimerList.RemoveAt(0);
                    FiniteStateMachineBase fsm =
                        (FiniteStateMachineBase) this._FsmList[timerEvt.FiniteStateMachine.GetHashCode()];
                    if(fsm !=null)
                    {
                        System.Threading.Monitor.Exit(this);
                        try
                        {
                            fsm.OnFsmEvent(timerEvt);
                            if(timerEvt.Repeats==0)
                            {
                                timerEvt.Done();
                            }
                            else
                            {
                                timerEvt.DecRepeats();
                                this.PushEvent(timerEvt,timerEvt.FiniteStateMachine);
                            }
                        }
                        finally
                        {
                            System.Threading.Monitor.Enter(this);
                        }
                    }
                }
            }

            if(this._TimerList.Count==0)
            {
                this._ExpiryTime = DateTime.MaxValue;
            }
        }

        private void ThreadProc()
        {
            lock(this)
            {
                Thread.CurrentThread.Name = this._Name;
                Monitor.Pulse(this);
                bool allDone = false;
                this._ExpiryTime = DateTime.MaxValue;

                while(!allDone)
                {
                    bool timerEventExpired = false;
                    if(this._TimerList.Count !=0)
                    {
                        TimeSpan delta = this._ExpiryTime - DateTime.Now;
                        if(delta>TimeSpan.Zero)
                        {
                            timerEventExpired = !Monitor.Wait(this, delta);
                        }
                        else
                        {
                            timerEventExpired = true;
                            this._ExpiryTime = DateTime.MaxValue;
                        }
                    }
                    else
                    {
                        Monitor.Wait(this);
                    }

                    if(timerEventExpired || this._TimerListChanged)
                    {
                        this.CheckTimerEvents();
                    }

                    while(this._JobQueue.Count>0)
                    {
                        FsmEventBase jobObj = (FsmEventBase) this._JobQueue.Dequeue();
                        if(typeof(FiniteStateMachineBase).IsInstanceOfType(jobObj))
                        {
                            this._FsmList.Add(jobObj.GetHashCode(),jobObj);
                            System.Threading.Monitor.Exit(this);
                            try
                            {
                                ((FiniteStateMachineBase) jobObj).OnFsmEntry();
                                ((FiniteStateMachineBase) jobObj).EnterFirstState();
                            }
                            finally
                            {
                                System.Threading.Monitor.Enter(this);
                            }
                        }
                        else if(typeof(TerminateFsmEvent).IsInstanceOfType(jobObj))
                        {
                            TerminateFsmEvent evt = (TerminateFsmEvent) jobObj;
                            if(evt.FiniteStateMachine !=null)
                            {
                                this.RemoveFsm(evt.FiniteStateMachine);
                            }
                            else
                            {
                                this.RemoveAllFsms();
                            }
                        }
                        else if(typeof(TerminateEvent).IsInstanceOfType(jobObj))
                        {
                            allDone = true;
                        }
                        else if(typeof(FsmTimerEvent).IsInstanceOfType(jobObj))
                        {
                            this.PushEvent((FsmTimerEvent)jobObj, ((FsmTimerEvent)jobObj).FiniteStateMachine);
                            this.CheckTimerEvents();
                        }
                        else
                        {
                            FiniteStateMachineBase fsm =
                                (FiniteStateMachineBase)
                                this._FsmList[((FsmEvent) jobObj).FiniteStateMachine.GetHashCode()];
                            if(fsm !=null)
                            {
                                System.Threading.Monitor.Exit(this);
                                try
                                {
                                    fsm.OnFsmEvent((FsmEvent) jobObj);
                                    ((FsmEvent)jobObj).Done();
                                }
                                finally
                                {
                                    System.Threading.Monitor.Enter(this);
                                }
                            }
                            else
                            {
                                Trace.WriteLine("FSM not found for FsmEvent "+jobObj.ToString());
                            }
                        }

                        if(this._TimerListChanged)
                        {
                            this.CheckTimerEvents();
                        }
                    }   // while 

                    Monitor.Pulse(this);
                }   // while

                this.RemoveAllFsms();
            }   // lock
        }

        private void RemoveFsm(FiniteStateMachineBase fsm)
        {
            this.PopFsm(fsm);
            System.Threading.Monitor.Exit(this);
            try
            {
                fsm.OnFsmExit();
                fsm.Done();
            }
            finally
            {
                System.Threading.Monitor.Enter(this);
            }
        }

        private void RemoveAllFsms()
        {
            Hashtable fsmsHt = (Hashtable) this._FsmList.Clone();
            foreach(FiniteStateMachineBase fsm in fsmsHt.Values)
            {
                this.RemoveFsm(fsm);
            }
        }

        public void Dispose()
        {
            this.PushEvent(new TerminateEvent(),null);
        }
    }
}
